/*
 * Reksio - Memory Map Editor
 * Copyright (C) 2023 CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In applying this licence, CERN does not waive the privileges and immunities
 * granted to it by virtue of its status as an Intergovernmental Organization or
 * submit itself to any jurisdiction.
 */

#include "attributecontainer.h"
#include "attribute.h"
#include "memorynode.h"
#include "validatornode.h"
#include "attributecontainervalidator.h"
#include <algorithm>

AttributeContainer::AttributeContainer(const std::string & name):
    name_(name)
{
}

AttributeContainer::AttributeContainer(const AttributeContainer &rhs):
    name_(rhs.name_),
    attributes_(),
    containers_(),
    parent_(rhs.parent_),
    parent_node_(rhs.parent_node_)
{
    copy(rhs);
    updateParent();
}

AttributeContainer &AttributeContainer::operator=(const AttributeContainer &rhs)
{
    name_ = rhs.name_;
    attributes_.clear();
    containers_.clear();
    parent_ = rhs.parent_;
    parent_node_ = rhs.parent_node_;

    copy(rhs);
    updateParent();
    return *this;
}

AttributeContainer &AttributeContainer::operator=(AttributeContainer &&rhs)
{
    name_ = std::move(rhs.name_);
    attributes_ = std::move(rhs.attributes_);
    containers_ = std::move(rhs.containers_);
    parent_ = std::exchange(rhs.parent_, nullptr);
    parent_node_ = std::exchange(rhs.parent_node_, nullptr);
    updateParent();
    return *this;
}

AttributeContainer::AttributeContainer(AttributeContainer &&rhs):
    name_(std::move(rhs.name_)),
    attributes_(std::move(rhs.attributes_)),
    containers_(std::move(rhs.containers_)),
    parent_(std::exchange(rhs.parent_, nullptr)),
    parent_node_(std::exchange(rhs.parent_node_, nullptr))
{
    updateParent();
}

bool AttributeContainer::isRoot() const
{
    return parent_node_ != nullptr;
}

AttributeContainer *AttributeContainer::getRoot()
{
    AttributeContainer* parent = this;
    while(parent->parent_)
    {
        parent = parent->parent_;
    }
    return parent;
}

const AttributeContainer *AttributeContainer::getRoot() const
{
    const AttributeContainer* parent = this;
    while(parent->parent_)
    {
        parent = parent->parent_;
    }
    return parent;
}

const std::string &AttributeContainer::getName() const
{
    return name_;
}

const AttributeContainer::attributes_type &AttributeContainer::getAttributes() const
{
    return attributes_;
}

const AttributeContainer::containers_type &AttributeContainer::getContainers() const
{
    return containers_;
}

int AttributeContainer::getAttributesSize() const
{
    return static_cast<int>(attributes_.size());
}

int AttributeContainer::getContainersSize() const
{
    return static_cast<int>(containers_.size());
}

int AttributeContainer::getTotalSize() const
{
    int size = getAttributesSize();
    for(const auto& container: containers_)
        size += container->getTotalSize();
    return size;
}

Attribute *AttributeContainer::getAttribute(const int index) const
{
    if(index >= 0 && index < getAttributesSize())
        return attributes_[static_cast<size_t>(index)].get();
    return nullptr;
}

Attribute *AttributeContainer::getAttribute(const std::vector<std::string> &name) const
{
    if(name.empty())
        return nullptr;
    if(name.size() == 1)
        return getAttribute(name[0]);
    const std::string& attr_name = *(name.end()-1);
    std::vector<std::string> container_names(name.begin(), name.end()-1);
    AttributeContainer* container = getAttributeContainer(container_names);
    if(container)
        return container->getAttribute(attr_name);
    return nullptr;
}

Attribute *AttributeContainer::getAttribute(const std::string &name) const
{
    const auto& attr_it = findAttribute(name);
    if(attr_it != attributes_.end())
        return attr_it->get();
    return nullptr;
}

std::vector<Attribute *> AttributeContainer::getAllAttributes() const
{
    std::vector<Attribute*> result;
    for(const auto& attribute: attributes_)
        result.push_back(attribute.get());
    for(const auto& container: containers_)
    {
        const auto container_results = container->getAllAttributes();
        result.insert(result.end(), container_results.begin(), container_results.end());
    }
    return result;
}

std::vector<AttributeContainer *> AttributeContainer::getAllAttributeContainers() const
{
    std::vector<AttributeContainer*> result;
    result.push_back(const_cast<AttributeContainer*>(this));
    for(const auto& container: containers_)
    {
        const auto container_results = container->getAllAttributeContainers();
        result.insert(result.end(), container_results.begin(), container_results.end());
    }
    return result;
}



AttributeContainer *AttributeContainer::getAttributeContainer(const int index) const
{
    if(index >= 0 && index < getContainersSize())
        return containers_[static_cast<size_t>(index)].get();
    return nullptr;
}

AttributeContainer *AttributeContainer::getAttributeContainer(const std::string &name) const
{
    for(const auto& container: containers_)
    {
        if(container->getName() == name)
            return container.get();
    }
    return nullptr;
}

AttributeContainer *AttributeContainer::getAttributeContainer(const std::vector<std::string> &name) const
{
    if(name.empty())
        return const_cast<AttributeContainer*>(this);
    for(const auto& container: containers_)
    {
        if(container->getName() == name[0])
            return container->getAttributeContainer(std::vector<std::string>(name.begin()+1, name.end()));
    }
    return nullptr;
}

void AttributeContainer::addAttribute(std::unique_ptr<Attribute> attr)
{
//    if(!getValidator()->getAttributeValidator(attr->getName()))
//        return;
    attr->setParent(this);
    attributes_.push_back(std::move(attr));
}

void AttributeContainer::addAttribute(Attribute *attr)
{
    addAttribute(std::make_unique<Attribute>(*attr));
}

bool AttributeContainer::removeAttribute(const std::string &name)
{
    const auto& attr_pos = findAttribute(name);
    if(attr_pos != attributes_.end())
    {
        attributes_.erase(attr_pos);
        return true;
    }
    return false;
}

bool AttributeContainer::removeAttribute(Attribute &attr)
{
    return removeAttribute(attr.getName());
}

bool AttributeContainer::removeAttribute(const int &position)
{
    if(position < 0)
        return false;
    const auto iter_position = attributes_.begin() + position;
    if(iter_position > attributes_.end())
        return false;
    attributes_.erase(iter_position);
    return true;
}

std::unique_ptr<Attribute> AttributeContainer::releaseAttribute(const int position)
{
    if(position >= 0 && position < getAttributesSize())
        return std::unique_ptr<Attribute>(attributes_[static_cast<size_t>(position)].release());
    return nullptr;
}

AttributeContainer::attributes_type::const_iterator AttributeContainer::findAttribute(const std::string &name) const
{
    return std::find_if(attributes_.begin(), attributes_.end(), [&name](const auto& i){return i->getName() == name;});
}

AttributeContainer::attributes_type::iterator AttributeContainer::findAttribute(const std::string &name)
{
    return std::find_if(attributes_.begin(), attributes_.end(), [&name](const auto& i){return i->getName() == name;});
}

bool AttributeContainer::hasAttribute(const std::string &name) const
{
    return findAttribute(name) != attributes_.end();
}

bool AttributeContainer::hasAttribute(const std::vector<std::string> &name) const
{
    return getAttribute(name) != nullptr;
}

int AttributeContainer::attributeIndex(const std::string &attribute_name) const
{
    return static_cast<int>(std::distance(attributes_.begin(), findAttribute(attribute_name)));
}

int AttributeContainer::containerIndex() const
{
    if(parent_ != nullptr)
    {
        const auto i = std::find_if(parent_->containers_.begin(), parent_->containers_.end(), [this](auto& i){return this == i.get(); });
        if (i != parent_->containers_.end())
        {
            return static_cast<int>(std::distance(parent_->containers_.begin(), i));
        }
    }
    return 0;
}

int AttributeContainer::containerIndex(const std::string &container_name) const
{
    AttributeContainer * container = getAttributeContainer(container_name);
    if(container)
        return container->containerIndex();
    return -1;
}

void AttributeContainer::setName(const std::string &new_name)
{
    name_ = new_name;
}

std::vector<std::string> AttributeContainer::getFullName() const
{
    std::vector<std::string> full_name;
    if(isRoot())
        return full_name; // return empty
    full_name.push_back(getName());
    const AttributeContainer* parent = parent_;
    while(parent)
    {
        if(!parent->isRoot())
            full_name.insert(full_name.begin(), parent->getName());
        parent = parent->parent_;
    }
    return full_name;
}

bool AttributeContainer::isValid() const
{
    return getValidator()->isValid(this);
}

void AttributeContainer::addContainer(std::unique_ptr<AttributeContainer> attr_container)
{
    attr_container->setParent(this);
    containers_.push_back(std::move(attr_container));
}

void AttributeContainer::addContainer(AttributeContainer *attr_container)
{
    addContainer(std::make_unique<AttributeContainer>(*attr_container));
}

void AttributeContainer::addContainer(const std::string &name)
{
    addContainer(std::make_unique<AttributeContainer>(name));
}

void AttributeContainer::addContainers(const std::vector<std::string> names)
{
    if(names.empty())
        return;
    const std::string& container_name = names[0];
    AttributeContainer * container = getAttributeContainer(container_name);
    if(!container)
    {
        std::unique_ptr<AttributeContainer> new_container = std::make_unique<AttributeContainer>(container_name);
        container = new_container.get();
        addContainer(std::move(new_container));
    }
    if(names.size()>1)
        container->addContainers(std::vector<std::string>(names.begin()+1, names.end()));
}

bool AttributeContainer::removeAttributeContainer(const int &position)
{
    if(position < 0)
        return false;
    const auto iter_position = containers_.begin() + position;
    if(iter_position > containers_.end())
        return false;
    containers_.erase(iter_position);
    return true;
}

std::unique_ptr<AttributeContainer> AttributeContainer::releaseAttributeContainer(const int position)
{
    if(position >= 0 && position < getContainersSize())
        return std::unique_ptr<AttributeContainer>(containers_[static_cast<size_t>(position)].release());
    return nullptr;
}

bool AttributeContainer::hasContainer(const std::string &name) const
{
    AttributeContainer* container = getAttributeContainer(name);
    return container != nullptr;
}

AttributeContainerValidator* AttributeContainer::getValidator() const
{
    if(isRoot())
        return parent_node_->getValidator()->getAttributes();
    return parent_->getValidator()->getContainerValidator(getName());
}

const std::vector<std::pair<Attribute *, std::vector<INodeRuleValidator*> > > AttributeContainer::getFailedValidators() const
{
    std::vector<std::pair<Attribute*, std::vector<INodeRuleValidator*>>> result;
    for(const auto& attribute: attributes_)
    {
        if(!attribute->isValid())
            result.push_back(std::make_pair(attribute.get(), attribute->getFailedValidators()));
    }
    for(const auto& container: containers_)
    {
        auto res = container->getFailedValidators();
        result.insert(result.end(), res.begin(), res.end());
    }
    return result;
}

std::vector<AttributeValidator*> AttributeContainer::getMissingAttributes() const
{
    return getValidator()->missingAttributes(this);
}

void AttributeContainer::setParent(MemoryNode *node)
{
    parent_node_ = node;
}

void AttributeContainer::setParent(AttributeContainer *attr_container)
{
    parent_ = attr_container;
}

AttributeContainer *AttributeContainer::getParent() const
{
    return parent_;
}

MemoryNode *AttributeContainer::getParentNode() const
{
    if(isRoot())
        return parent_node_;
    return getRoot()->getParentNode();
}

bool AttributeContainer::isDeprecated() const
{
    return getValidator()->isDeprecated();
}

bool AttributeContainer::hasDeprecated() const
{
    if(isDeprecated())
        return true;
    for(const auto& attr: attributes_)
    {
        if(attr->isDeprecated())
            return true;
    }
    for(const auto& container: containers_)
    {
        if(container->hasDeprecated())
            return true;
    }
    return false;
}

std::vector<Attribute *> AttributeContainer::getDeprecatedAttributes() const
{
    std::vector<Attribute*> result;
    for(const auto& attr: attributes_)
    {
        if(attr->isDeprecated())
            result.push_back(attr.get());
    }
    for(const auto& container: containers_)
    {
        auto res = container->getDeprecatedAttributes();
        result.insert(result.end(), res.begin(), res.end());
    }
    return result;
}

std::vector<AttributeContainer *> AttributeContainer::getDeprecatedAttributeContainers() const
{
    std::vector<AttributeContainer*> result;
    if(isDeprecated())
        result.push_back(const_cast<AttributeContainer*>(this));
    for(const auto& container: containers_)
    {
        auto res = container->getDeprecatedAttributeContainers();
        result.insert(result.end(), res.begin(), res.end());
    }
    return result;
}

void AttributeContainer::updateParent()
{
    for(auto& attribute: attributes_)
    {
        attribute->setParent(this);
    }

    for(auto& container: containers_)
    {
        container->parent_ = this;
    }
}

void AttributeContainer::copy(const AttributeContainer &rhs)
{
    // deep copy
    for(const auto& attribute: rhs.attributes_)
        attributes_.push_back(std::make_unique<Attribute>(*attribute));
    for(const auto& container: rhs.containers_)
        containers_.push_back(std::make_unique<AttributeContainer>(*container));
}

std::ostream &operator<<(std::ostream &stream, const AttributeContainer &attribute_container)
{
    stream << attribute_container.getName();

    return stream;
}
